`timescale 1ns / 1ps
/*
 * Copyright (c) 2020-2021, SERI Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2021-10-29     Lyons        first version
 */

module pa_perips_timer (
    clk_i,                      // system clock
    rst_n_i,                    // system reset

    addr_i,                     // register address, width 8-bits

    data_we_i,                  // write or read
    data_i,                     // write data

    data_o,                     // read data

    irq_o                       // interrupt require
    );

`include "../pa_chip_param.v"

localparam TIMER_REG_CR         = 8'h00;
localparam TIMER_REG_SR         = 8'h04;
localparam TIMER_REG_PSC        = 8'h08;
localparam TIMER_REG_LOAD       = 8'h0c;
localparam TIMER_REG_COUNT      = 8'h10;

input                           clk_i;
input                           rst_n_i;

input  [7:0]                    addr_i;

input                           data_we_i;
input  [`DATA_BUS_WIDTH-1:0]    data_i;

output [`DATA_BUS_WIDTH-1:0]    data_o;

output                          irq_o;

wire                            clk_i;
wire                            rst_n_i;

wire [7:0]                      addr_i;

wire                            data_we_i;
wire [`DATA_BUS_WIDTH-1:0]      data_i;

wire [`DATA_BUS_WIDTH-1:0]      data_o;

wire                            irq_o;


// [0]: enbale
reg  [`DATA_BUS_WIDTH-1:0]      _timer_cr;

// [0]: timing-up flag
reg  [`DATA_BUS_WIDTH-1:0]      _timer_sr;

// [31:0] : prescale, need sub 1
reg  [`DATA_BUS_WIDTH-1:0]      _timer_psc;

// [31:0] : load value
reg  [`DATA_BUS_WIDTH-1:0]      _timer_load;

// [31:0] : count
reg  [`DATA_BUS_WIDTH-1:0]      _timer_count;


reg  [`DATA_BUS_WIDTH-1:0]      timer_clk_cnt;

wire                            timer_clk_timeup;

assign timer_clk_timeup = (timer_clk_cnt == _timer_psc[31:0]);

always @ (posedge clk_i) begin
    if (~rst_n_i) begin
        timer_clk_cnt <= `ZERO_WORD;
    end
    else if (_timer_cr[0]) begin
        if (timer_clk_timeup) begin
            timer_clk_cnt <= `ZERO_WORD;
        end
        else begin
            timer_clk_cnt <= timer_clk_cnt + 32'h1;
        end
    end
    else begin
        timer_clk_cnt <= `ZERO_WORD;
    end
end

reg                             timer_timeup;

always @ (posedge clk_i) begin
    if (~rst_n_i) begin
        timer_timeup <= 1'b0;
    end
    else if (_timer_cr[0]) begin
        if (timer_timeup) begin
            timer_timeup <= 1'b0;
        end
        else if (_timer_count == 32'h0) begin
            timer_timeup <= 1'b1;
        end
    end
    else begin
        timer_timeup <= 1'b0;
    end
end

always @ (posedge clk_i) begin
    if (~rst_n_i) begin
        _timer_count <= _timer_load;
    end
    else if (_timer_cr[0]) begin
        if (_timer_count == 32'h0) begin
            _timer_count <= _timer_load; //re-load timer value
        end
        else if (timer_clk_timeup) begin
            _timer_count <= _timer_count - 32'h1;
        end
    end
    else begin
        _timer_count <= _timer_load;
    end
end

always @ (posedge clk_i) begin
    if (~rst_n_i) begin
        _timer_cr   <= `ZERO_WORD;
        _timer_sr   <= `ZERO_WORD;
        _timer_psc  <= `ZERO_WORD;
        _timer_load <= `ZERO_WORD;
    end
    else begin
        if (data_we_i) begin
        case (addr_i[7:0])
            TIMER_REG_CR   : _timer_cr   <= {data_i[31:0]};
            TIMER_REG_SR   : _timer_sr   <= {data_i[31:1], (_timer_sr[0] & ~data_i[0])};
            TIMER_REG_PSC  : _timer_psc  <= {data_i[31:0]};
            TIMER_REG_LOAD : _timer_load <= {data_i[31:0]};
        endcase
        end
        else if (_timer_sr[0]) begin
            _timer_sr <= {data_i[31:1], 1'b0};
        end
        else begin
            if (_timer_cr[0] && timer_timeup) begin
                _timer_sr <= {data_i[31:1], 1'b1};
            end
        end
    end
end

reg  [`DATA_BUS_WIDTH-1:0]      _data;

always @ (posedge clk_i) begin
    case (addr_i[7:0])
        TIMER_REG_CR    : _data <= _timer_cr;
        TIMER_REG_SR    : _data <= _timer_sr;
        TIMER_REG_PSC   : _data <= _timer_psc;
        TIMER_REG_LOAD  : _data <= _timer_load;
        TIMER_REG_COUNT : _data <= _timer_count;
        default         : _data <= `ZERO_WORD;
    endcase
end

assign data_o[`DATA_BUS_WIDTH-1:0] = _data[`DATA_BUS_WIDTH-1:0];

assign irq_o = _timer_sr[0];

endmodule